#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
    实现对邮件的构造和解析功能，以及bayes算法前端计算部分
与后端通讯部分。
    Let's go!!!
    ps:解析邮件中的发送方和接收方地址解析未优化,在此注明
将会使用email.util中方法做优化。
"""


from email.parser import *
from email.header import decode_header
from email.header import Header
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from bs4 import BeautifulSoup
import socket
from concurrent.futures import ThreadPoolExecutor, wait


class ClientEmailBox:
    def __init__(self, bayes_server_address=None, bayes_server_port=None):
        """
        初始化方法；
        :param bayes_server_address: 贝叶斯服务器地址
        :param bayes_server_port: 贝叶斯服务器地址
        """
        self.__email_list_header = {}  # 存放邮件头部信息 {1:{'subject':'titile','From':'...',...},2:{}}
        self.__email_list_content = []  # 存放邮件内容,下标与header中key值一致
        self.__email_list_content_text= [] # 存放去除html便签的邮件文本内容
        self.__email_list_class = {}  # 邮件分类结果，{1:'ham',2:'spam',...}
        self.__bayes_server_address = bayes_server_address # 贝叶斯服务器地址
        self.__bayes_server_port = bayes_server_port # 贝叶斯服务器端口号

    def set_bayes_server(self, address, port):
        self.__bayes_server_address = address
        self.__bayes_server_port = port

    def get_emails(self):
        """
        获取邮箱中解析好的邮件报头和邮件内容
        :return: （邮件报头,邮件内容）
        """
        header = self.__email_list_header
        content = self.__email_list_content
        text = self.__email_list_content_text
        return header, content, text

    def get_count_email(self):
        """
        得到信箱中邮件数量。
        :return:邮件数
        """
        return len(self.__email_list_header)

    def get_email_class(self):
        """
        得到信箱中的分类信息。
        :return:邮件类型列表
        """
        return self.__email_list_class

    def __get_charset(self, msg):
        """
        私有方法，得到邮件内容类型，辅助解析邮件使用；
        :param msg: email.message对象
        :return:String 编码类型
        """
        charset = msg.get_charset()
        if charset is None:
            content_type = msg.get('Content-Type', '').lower()
            pos = content_type.find('charset=')
            if pos >= 0:
                charset = content_type[pos + 8:].strip()
        return charset

    def parser_email(self, email_list):
        """
            解析pop接收到的消息列表，解析成邮件，邮件头信息放在self.__email_list_header中，
        邮件内容放置于self.__email_list_content中，格式如下
        __email_list_header：{0:{'subject':'titile',...},1:{'subject':'titile',...},...}
        __email_list_content：['邮件0','邮件1',...]
        :param email_list:pop接收到的邮件消息列表
        :return:(True/False,'success!'/err message)
        """
        try:
            for index in range(0, len(email_list)):
                head = {}
                bytes_msg = b'\r\n'.join(email_list[index][1])
                bytes_msg = BytesParser().parsebytes(bytes_msg)
                for key in bytes_msg.keys():
                    value_charset = bytes_msg.get(key, '')
                    value, charset = decode_header(value_charset)[0]
                    if charset:
                        try:
                            head[key] = value.decode(charset, "ignore")
                        except Exception as err:
                            print(err)
                            head[key] = value.decode("ascii", "ignore")
                    else:
                        head[key] = value
                self.__email_list_header[index] = head
                # 解析邮件消息头部内容

                charset = self.__get_charset(bytes_msg)
                if bytes_msg.is_multipart():
                    # 判断是否需要解码
                    while True:
                        bytes_msg = bytes_msg.get_payload()[0]
                        bytes_msg_par = bytes_msg.get_payload(decode=True)
                        if bytes_msg_par is not None:
                            break
                    if charset:
                        str_msg = bytes_msg_par.decode(charset, "ignore")
                    else:
                        str_msg = bytes_msg_par.decode(errors="ignore")
                else:
                    str_msg = bytes_msg.get_payload(decode=True) \
                        .decode(charset, "ignore")
                self.__email_list_content.append(str_msg)
                # 解析含有html标签的字符串邮件，并写入列表
                soup = BeautifulSoup(str_msg,"lxml")
                content_text = soup.get_text()
                self.__email_list_content_text.append(content_text)
            return True, 'success!'
        except Exception as err:
            return False, str(err)
            # 解析邮件内容消息部分

    def build_email(self, from_addr, to_addr, subject, content):
        """
        构造邮件，目前只能构造普通文字邮件
        :param from_addr: 发送地址
        :param to_addr: 目标地址
        :param subject: 邮件主题
        :param content: 邮件内容
        :return: message.as_string();即字符串，可直接交给smtp发送
        """
        msg = MIMEMultipart('mixed')
        msg['From'] = Header(from_addr, "utf-8")
        msg['To'] = Header(to_addr, "utf-8")
        msg['subject'] = Header(subject, "utf-8")
        msg.attach(MIMEText(content, 'plain', 'utf-8'))
        return msg.as_string()

    def __send_to_bayes_server(self, index, email):
        """
        与后端server交互方法
        将邮件发送至贝叶斯服务器，交互过程两步确认：
        1、发送邮件长度，等待后端回应ready；
        2、发送邮件，等待后端回应邮件分类
        填入__email_list_class字典中
        :param index: 邮件标号，即下标
        :param email: 邮件正文
        :return:无
        """
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((self.__bayes_server_address,
                               self.__bayes_server_port))
        email = email.encode("utf-8")
        email_length = str(len(email)).encode('utf-8')
        client_socket.send(email_length)
        print('number.'+str(index)+"email's length send...")
        response = client_socket.recv(1024).decode('utf-8')
        print('Get number.'+str(index)+' response:'+response)
        if response == 'Ready':
            client_socket.send(email)
            print("number."+str(index)+"email send...")
            response = client_socket.recv(1024).decode('utf-8')
            self.__email_list_class[index] = response
            client_socket.close()
        else:
            client_socket.close()

    def set_email_class(self):
        """
        创建多线程，一个线程对应一封邮件发送过程，提高并发性提高效率；
        :return: True(邮箱有邮件,完成分类)/False(邮箱无邮件)
        """
        if len(self.__email_list_header) != 0:
            pool = ThreadPoolExecutor(max_workers=10)
            email_futures = []
            for index, email in enumerate(self.__email_list_content_text):
                email_future = pool.submit(self.__send_to_bayes_server,
                                           (index, email))
                email_futures.append(email_future)
            wait(email_futures)
            return True
        else:
            return False
